package com.ygq.seckill.controller;

import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ramostear.captcha.HappyCaptcha;
import com.ramostear.captcha.common.Fonts;
import com.ramostear.captcha.support.CaptchaStyle;
import com.ramostear.captcha.support.CaptchaType;
import com.ygq.seckill.config.RedisConfig;
import com.ygq.seckill.entity.Order;
import com.ygq.seckill.entity.SeckillMessage;
import com.ygq.seckill.entity.SeckillOrder;
import com.ygq.seckill.entity.User;
import com.ygq.seckill.rabbitmq.MQSenderMessage;
import com.ygq.seckill.service.GoodsService;
import com.ygq.seckill.service.OrderService;
import com.ygq.seckill.service.SeckillOrderService;
import com.ygq.seckill.vo.GoodsVo;
import com.ygq.seckill.vo.RespBean;
import com.ygq.seckill.vo.RespBeanEnum;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Controller
@RequestMapping("/seckill")
public class SeckillController implements InitializingBean {
    @Resource
    private GoodsService goodsService;
    @Resource
    private SeckillOrderService seckillOrderService;
    @Resource
    private OrderService orderService;
    @Resource
    private RedisTemplate redisTemplate;

    //装配 RedisScript
    @Resource
    private RedisScript<Long> script;

    @Resource
    private MQSenderMessage mqSenderMessage;

    //若某个商品库存为空，则标记到entryStockMap
    private HashMap<Long,Boolean>entryStockMap=new HashMap<>();

    /**
     *压测
     */
//    @RequestMapping("/doSeckill")
//    public String doSeckill(Model model, User user, Long goodsId) {
//        if (user == null) {//用户没有登录
//            return "login";
//        }
//        //将user放入到model, 下一个模板可以使用
//        model.addAttribute("user", user);
//        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);
//
//        //判断库存
//        if (goodsVo.getStockCount() < 1) {//没有库存
//            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
//            return "secKillFail";//错误页面
//        }
//        SeckillOrder o = (SeckillOrder) redisTemplate.opsForValue()
//                .get("order:" + user.getId() + ":" + goodsVo.getId());
//        if (null != o) { //说明该用户已经抢购了该商品
//            model.addAttribute("errmsg", RespBeanEnum.REPEAT_ERROR.getMessage());
//            return "secKillFail";//错误页面
//        }
//        if (!entryStockMap.get(goodsId)) {
//            model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
//            return "secKillFail";//错误页面
//        }
//        String uuid = UUID.randomUUID().toString();
//        Boolean lock =
//                redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
//        //2 获取锁成功
//        if (lock) {
//            //写自己的业务-就可以有多个操作了
//            Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);
//            if (decrement < 0) {//说明这个商品已经没有库存
//                //说明当前秒杀的商品，已经没有库存
//                entryStockMap.put(goodsId, true);
//                //恢复库存为0
//                redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);
//                redisTemplate.execute(script, Arrays.asList("lock"), uuid);
//                model.addAttribute("errmsg", RespBeanEnum.ENTRY_STOCK.getMessage());
//                return "secKillFail";//错误页面
//            }
//            //释放分布式锁
//            redisTemplate.execute(script, Arrays.asList("lock"), uuid);
//        } else {
//            //3 获取锁失败,返回个信息[本次抢购失败，请再次抢购...]
//            model.addAttribute("errmsg", RespBeanEnum.SEC_KILL_RETRY.getMessage());
//            return "secKillFail";//错误页面
//        }
//        SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
//        mqSenderMessage.senderMessage(JSONUtil.toJsonStr(seckillMessage));
//        model.addAttribute("errmsg", "排队中...");
//        return "secKillFail";
//    }
    // 正规使用
    @RequestMapping(value = "/{path}/doSeckill")
    @ResponseBody
    public RespBean doSeckill(@PathVariable String path, Model model, User user, Long goodsId) {


        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        System.out.println("客户端path: " + path);
        boolean b = orderService.checkPath(user, goodsId, path);
        if (!b) {
            return RespBean.error(RespBeanEnum.REQUEST_ILLEGAL);
        }
        GoodsVo goodsVo = goodsService.findGoodsVoByGoodsId(goodsId);

        if (goodsVo.getStockCount() < 1) { //判断库存
            return RespBean.error(RespBeanEnum.ENTRY_STOCK);
        }
        //解决重复抢购, 从 redis 中取出订单, 提高效率
        SeckillOrder o = (SeckillOrder) redisTemplate.opsForValue()
                .get("order:" + user.getId() + ":" + goodsVo.getId());
        if (o != null) {
            return RespBean.error(RespBeanEnum.REPEAT_ERROR);
        }

        if (!entryStockMap.get(goodsId)) { //map:false =null->no stock
            return RespBean.error(RespBeanEnum.ENTRY_STOCK);
        }


            //1 获取锁，setnx 得到一个 uuid 值，作为锁的值
            String uuid = UUID.randomUUID().toString();
            Boolean lock =
                    redisTemplate.opsForValue().setIfAbsent("lock", uuid, 3, TimeUnit.SECONDS);
            //2 获取锁成功、就执行相应业务
            if (lock) {
                //库存预减 优化解决超卖问题 在redis判断 若商品已经无，则直接返回，否则放行到数据库-1
                //这个 decrement 方法具有原子性
                Long decrement = redisTemplate.opsForValue().decrement("seckillGoods:" + goodsId);

                if (decrement < 0) {
                    //这里使用内存标记，避免多次操作 redis, true 表示空库存了.
                    entryStockMap.put(goodsId, false);
                    redisTemplate.opsForValue().increment("seckillGoods:" + goodsId);    //恢复成 0
                    redisTemplate.execute(script, Arrays.asList("lock"), uuid);
                    return RespBean.error(RespBeanEnum.ENTRY_STOCK);
                }

                redisTemplate.execute(script, Arrays.asList("lock"), uuid);
                //抢购，像消息队列发送秒杀请求，实现秒杀异步请求.发出秒杀消息后，立即返回结果
                //客户端最终通过轮询，获取到最终结果
                SeckillMessage seckillMessage = new SeckillMessage(user, goodsId);
                mqSenderMessage.senderMessage(JSONUtil.toJsonStr(seckillMessage));
                return RespBean.error(RespBeanEnum.SEC_KILL_WAIT);

            } else {
                return RespBean.error(RespBeanEnum.SEC_KILL_RETRY);
            }
    }

//
    //生成校验码
    @GetMapping("/captcha")
    public void happyCaptcha(User user, Long goodsId, HttpServletRequest request, HttpServletResponse response) {
        System.out.println("请求来了... goodsId=" + goodsId);
        HappyCaptcha.require(request, response)
                .style(CaptchaStyle.ANIM) //设置展现样式为动画
                .type(CaptchaType.NUMBER) //设置验证码内容为数字
                .length(6) //设置字符长度为 6
                .width(220) //设置动画宽度为 220
                .height(80) //设置动画高度为 80
                .font(Fonts.getInstance().zhFont()) //设置汉字的字体
                .build().finish(); //生成并输出验证码
//把验证码的值存放到 Redis 中, userId+goodsId, 有效时间为 100s
        redisTemplate.opsForValue().set
                ("captcha:" + user.getId() + ":" + goodsId, (String)
                                request.getSession().getAttribute("happy-captcha"),
                        100, TimeUnit.SECONDS);
    }

    @RequestMapping(value = "/path", method = RequestMethod.GET)
    @ResponseBody
    public RespBean getPath(User user, Long goodsId,String captcha,HttpServletRequest request) {
        if (user == null) {
            return RespBean.error(RespBeanEnum.SESSION_ERROR);
        }
        //也可以使用注解提高使用通用性
        String uri = request.getRequestURI();
        ValueOperations valueOperations = redisTemplate.opsForValue();
        Integer count = (Integer) valueOperations.get(uri + ":" + user.getId());
        if (count == null) {
            valueOperations.set(uri + ":" + user.getId(), 1, 5, TimeUnit.SECONDS);
        } else if (count < 5) {
            valueOperations.increment(uri + ":" + user.getId());
        } else {
            return RespBean.error(RespBeanEnum.ACCESS_LIMIT_REACHED);
        }
        //验证用户输入的验证码
        boolean check = orderService.checkCaptcha(user, goodsId, captcha);
        if (!check) {
            return RespBean.error(RespBeanEnum.CAPTCHA_ERROR);
        }
        //创建真正的地址
        String url = orderService.createPath(user, goodsId);
        return RespBean.success(url);
    }

    //bean 初始化要执行的方法，把商品库存数量加载到 redis 中,设置map为true 表示有库存   // 类似与切面编程
    @Override
    public void afterPropertiesSet() throws Exception {
        List<GoodsVo> goodsVos = goodsService.findGoodsVo();
        if(CollectionUtils.isEmpty(goodsVos)){//如果没有秒杀商品，直接返回
            return;
        }
        goodsVos.forEach(goodsVo -> {//有则遍历并将数据 -》redis
            redisTemplate.opsForValue().set("seckillGoods:"+goodsVo.getId(),goodsVo.getStockCount());
            entryStockMap.put(goodsVo.getId(),true);
        });
    }

}